{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.702891Z",
     "start_time": "2025-01-26T10:15:20.548860Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.891515Z",
     "start_time": "2025-01-26T10:15:23.703890Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.895383Z",
     "start_time": "2025-01-26T10:15:23.891515Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.942114Z",
     "start_time": "2025-01-26T10:15:23.895383Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.951346Z",
     "start_time": "2025-01-26T10:15:23.943105Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.959092Z",
     "start_time": "2025-01-26T10:15:23.952342Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.965667Z",
     "start_time": "2025-01-26T10:15:23.959092Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.970623Z",
     "start_time": "2025-01-26T10:15:23.966666Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 8  #过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.978100Z",
     "start_time": "2025-01-26T10:15:23.971614Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数a\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30), #30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30), #30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_outpaut.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        concat = torch.cat([x, deep_output], dim=1)  # x为wide输入\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "source": [
    "# train_ds[0][0]\n",
    "#验证模型是否正确\n",
    "input=train_ds[0][0].reshape(1, -1)\n",
    "print(input.shape)\n",
    "model=WideDeep()\n",
    "out=model(input)\n",
    "out.shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.986643Z",
     "start_time": "2025-01-26T10:15:23.978100Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 8])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 1])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.991504Z",
     "start_time": "2025-01-26T10:15:23.987643Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:23.997680Z",
     "start_time": "2025-01-26T10:15:23.991504Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 12
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:15:39.446162Z",
     "start_time": "2025-01-26T10:15:23.997680Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # update step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 10\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "c45bd346d9be45a9be9ef71b9d81e54e"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:16:50.183870Z",
     "start_time": "2025-01-26T10:16:50.094940Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhYAAAGwCAYAAAD16iy9AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAYIVJREFUeJzt3Qd4k+XaB/B/k+5F6WKWsvcGRUARZePEDSg4jh4H6nEd9XyK4D5OnLjFhThRj4KKylI2iuyyd6GF0r3SJt91P0lKWtrStG/W+/5/1/WatI3J+5Q0ufM8930/QTabzQYiIiIiDZi0uBMiIiIiwcCCiIiINMPAgoiIiDTDwIKIiIg0w8CCiIiINMPAgoiIiDTDwIKIiIg0Ewwvs1qtOHToEGJiYhAUFOTthyciIqJ6kLZXeXl5aN68OUwmk/8EFhJUpKSkePthiYiISAP79+9Hy5Yt/SewkJkK54nFxsZqdr8WiwU///wzRo4ciZCQEBiFUcdt5LFz3By3ERh13P489tzcXDUx4Hwf95vAwrn8IUGF1oFFZGSkuk9/+ofwNKOO28hj57g5biMw6rgDYeynSmNg8iYRERFphoEFERERaYaBBREREWnG6zkWRESkP9JKoLS0VNM8g+DgYBQXF6O8vBxGYvHR2CWfw2w2N/h+GFgQEVGDSECxe/duFVxo2TOhadOmqoLQaD2PbD4ce1xcnHrshjwuAwsiImrQm2B6err6pCuliLU1TnKHBCn5+fmIjo7W7D4DhdUHY5d/x8LCQmRkZKivmzVrVu/7YmBBRET1VlZWpt6QpBujlEhqvbQSHh5uyMCi1Adjj4iIUJcSXCQnJ9d7WcRY/1pERKQpZw5AaGior0+FNOAMDiXPo74YWBARUYMZLQ9Cr4I0+HdkYEFERESaYWBBREREmmFgQURE1ACtW7fGjBkzNLmvRYsWqaTJnJwcBCrdVIUczS9BRhFQWmaFH+7ZQkREfmTo0KHo3bu3JgHB6tWrERUVpcl56YFuAosxLy9DdlEwBp9ViK4twnx9OkREFMCkr4NUvEgHzFNJSkryyjkFCt0shUSF2ettC0rKfH0qRESGpRotlZZpchSVlrt1e3nsurj22muxePFivPTSS6oKQo5Zs2apy/nz56Nfv34ICwvD77//jp07d+Kiiy5CkyZNVMOq0047Db/88kutSyFBQUF45513MG7cOFW+2aFDB3z33Xf1/p1+9dVX6Natmzoneaznn3++0s9ff/119RjS90LO87LLLqv42ZdffokePXqoHhUJCQkYPnw4CgoK4Em6mbGICrUPpaDUWD3liYj8SZGlHF2n/uSTx9786ChEOt4LaiMBxbZt29C9e3c8+uij6nubNm1Slw888ACee+45tG3bFo0bN1ZttceOHYsnnnhCvbF/+OGHuOCCC5CWloZWrVrV+BjTp0/HM888g2effRavvPIKJk6ciL179yI+Pt6tMa1duxZXXHEFpk2bhiuvvBLLli3DrbfeqoIECZDWrFmDO+64Ax999BEGDRqErKwsLF26VP2/0hF1/Pjx6jwkyMnLy1M/q2sAVl/6CSw4Y0FERHXQqFEj1dBLZhNkXwyxdetWdSmBxogRIypuK4FAr169Kr5+7LHHMHfuXDUDMWXKlBof49prr1Vv6uLJJ5/Eyy+/jFWrVmH06NFunesLL7yAYcOG4eGHH1Zfd+zYEZs3b1YBizzGvn37VH7H+eefj5iYGKSmpqJPnz4VgYV0Rr3kkkvU94XMXniajgILx4xFCWcsiIh8JSLErGYOtGhrnZebh5jYmDq3tZbHbqj+/ftX+lr27JDZgh9++KHijbqoqEi9odemZ8+eFdfljT82NrZiHw53bNmyRS3FuBo8eLBaepEcEAmCJGiQGRYJWuRwLsFIQCRBiQQTo0aNwsiRI9UyiczEeJJ+cixCHTMWpZyxICLyFckvkOUILY6IULNbt9eia2TV6o57771XzVDIrIMsI6xbt069UZ9qi/iQKuWJcm5a7v7qJLMUf/75Jz799FO1cdjUqVNVQJGdna3KVhcsWKDyRrp27aqWZDp16qR2ovUk/QQWnLEgIqI6kqUQ5z4ntfnjjz/UkoPMAkhAIUsne/bsgbd06dJFnUPVc5IlEecmYVK5IkmZkkuxfv16dX6//fZbRUAjMxyS8/HXX3+pcUug5Ek6XArhjAUREdVOqitWrlyp3oSl2qOm2QSptvj6669Vwqa8SUuugydmHmpyzz33qEoUye2Q5M3ly5fj1VdfVZUg4vvvv8euXbswZMgQtcQxb948dX4yMyHj+/XXX9USiOxWKl9nZmaqYMWTdDNjEe1YCslnVQgREZ2CLHHIJ35ZIpA+FDXlTEjypLxhS8WFBBeSq9C3b1+vnWffvn3x+eefY86cOaqKRZY6JMFUZlFEXFycCnzOPfdcFTC88cYballEylMlr2PJkiWqqkVmOB566CFVqjpmzBiPnrPuZiyklpmIiKg28kYrn/5dOd+sq85sOJcVnG677bZKX1ddGrFVU84pOQ917QgqSzS5ubkV37v00kvVUZ0zzzxTtQGvjgQaP/74I7zNpL9yU85YEBER+YpJdw2ymGNBRER+6uabb1Y5HdUd8jM90NFSiLPclDMWRETknx599FGV31EdyYnQAx0FFpyxICIi/5acnKwOPdPRUghbehMREfmaSXczFlwKISIiCpzA4uDBg7j66qvVzmqyDat0IpPd1Xwt2hFY5HPGgoiIKDByLI4fP65ag55zzjmq97g0Fdm+fbvHNzRxZynEUm5DaZkVocG6mYwhIiLSZ2Dx3//+FykpKXj//fcrvtemTZta/5+SkhJ1ODmbflgsFnVoJcR0osVqdkERGkeGwgicv0Mtf5eBwqhj57g5bn8i5yUNoaSNtJatrp1Nppz3bSQ2H45dHk8eV/5dnXuRONX1ORhkq65FWA2k9am0Mz1w4AAWL16MFi1a4NZbb8WNN95Y4/8j283K5idVzZ49W23rqqV7V5phsQZhap8yJIRretdERFQN2QBLNuaSD52ywZVRyLbot9xyizpOpXHjxvj4449x3nnnwd/Jrq379+/H4cOH1RbxrgoLCzFhwgTk5OTUWhrr1oyFbHQyc+ZM3H333fjPf/6D1atX44477lBPpsmTJ1f7/zz44IPq9q4zFvIElE1RtKzZlUgqbM1vsFiB0wedhU5NY2AEMm7ZFnfEiBEnbdOrd0YdO8fNcfuT4uJi9UYkDZ7Cw7X7RCefefPy8tS24Fpsh641k8mkxlvX9zHJSazrbX05dvn3lHOVTc2q/nu6thmvTbC7UyT9+/dX+9KLPn36YOPGjWrTk5oCi7CwMHVUJX8gWv+RhJuAfFl+sdrv30g88fsMFEYdO8dtLP46btnXQt785I1WDq04lwCc9+2P3Dk3kxu/H1+OXR5PHre651tdn39unXGzZs3UckjVTU5q2hXO2xzNN5HP/UKIiHxDVtdLC7Q5LIXu3b6OK/tvvfUWmjdvflL+wkUXXYTrr78eO3fuVNebNGmiZmJk2/JffvlFs1/Rhg0b1G6kMjMgFZY33XQT8vPlY7GdbCo2bNgwNWMhu5dK0cTevXvVz/7++29VQCE/kxmQfv36+UVlZr1nLGRwaWlplb63bds2pKamwh+EOwILNskiIvIRCQaebN7gu5FPvXHu/k//OQSERp3yZpdffjluv/12LFy4UL2Bi6ysLLUT6Lx589SbvGw1/sQTT6gZ9w8//FBtmS7vf61atUJDFBQUqFzFgQMHqnSCjIwM/OMf/8CUKVMwa9YslddwySWX4JprrlFbpcvXq1atqlgSmThxolotkLQESa5ct26d381kuRVY3HXXXWpPelkKueKKK9RgJfKTw+fKLWgfdAC70IK9LIiIqNZkyjFjxqgiAmdg8eWXXyIxMVHNBshyQK9evSpu/9hjj2Hu3Ln47rvvVADQELNnz1Z5DBKsREXZg6BXX31VBS5SeSlBgiRHjh49Gu3atVPnIisDTrJCcN9996Fz587q6w4dOsDfuBVYyHSQ/HIlIVM2UpFS0xkzZqgIyqes5Qh+vj3etBRgMF7ijAURka+ERNpnDhpIlily8/IQGxNT9zwDeew6kvctqWh8/fXX1azEJ598gquuuko9lsxYSEXjDz/8gPT0dDVrUFRUpMmy/5YtW1TQ4gwqnKsBMl6ZEZGkSclZvPTSSzF8+HCVtCsf5CUVQUgxhMxwfPTRR+rnMvsiAYg/cTsr5Pzzz1frQxJxyS+otlJTrzGZgTj79FQH0wEGFkREviJT9rIcocUhgYI7t3ejgkJmCKT6QoIHqWpZunRpxYdk2X1UPkTL7Lx8X5YbpMu0lGJ6w3vvvYeff/5ZrRB89tln6NixI1asWKF+JgHPpk2bVOnqb7/9pvIe5Vz9iX+m2taDLamTuuwUdIDJm0REVCsppZRcBpmp+PTTT9GpUyf07dtX/eyPP/7Atddei3HjxqmAQvp07NmzR5PH7dKli0rAlFwLJ3k8mSmRc3Dtk/HAAw9g2bJl6N69u1pCcZJAQ1ITJPiQMbg2rfQH+gksEu3rTR05Y0FERHUgMxQyYyEzBK5L+pK38PXXX6uZCgkCpCmUVh0wJ06cqIIaWe6Qdg2SQCqJpJKsKVUou3fvVn2iJIdRKkEkeJCtMyQgkeUYyfGQqhH5mQQkkgDqmoMRcDkW/syWZP/Fdgg6gD8YWBAR0SlIyWd8fLzKbZDgwemFF15QZaeyFCEJnffff3+dm0OdinSc/umnn3DnnXeqvEX5WvIp5DGdP9+6dSs++OADVakiuRW33XYb/vnPf6pcj2PHjmHSpEk4cuSIOjeZsaiuu7Uv6SiwsE8hdQg6iIJi76yDERFR4JLlh0OHTk40bd26tcpfcCVv7q7cWRqxVemvIcsrVe/fSWYtZLZEAhnpU+GauCpdrmXZxt/pZikEjdvAghBEBJUisuiAr8+GiIjIkPQTWJjMOBZib8qSWLjb12dDREQGIMmf0p2zuqNbt24wIt0shYissBZoatmLpiW7fH0qRERkABdeeCEGDBhQ7c9C/KwjprfoKrDICWuhdiFrabH3VCciIvIk2bNDDtLjUogEFuEt1WWq1T82RSMiMoqqCYoUmLQoq9XVjEVhZAt12cZ2ELZyC4LMxpyGIiLyFpnulw2yMjMzkZSUVLFZlhZvcNLpUro8++u26Z5i9cHYJTCUx5R/R3lMqUCpL10FFpbwRBTawhAZVILijJ0Ib2ZvmkVERJ4hO2y2bNkSBw4c0Kw7pfONThpCydbiWgUrgcLmw7FLHw3ZwbUhAY2uAotQswnbbS3QK2gXStI3MbAgIvICqYCQbpUWi0Wz+5T7WrJkidqUy2hJkBYfjV2CxODg4AYHM7oKLExBwO6gFPTCLlgPbwZwqa9PiYjIEORNSQ4t7086TUr7a6MFFuYAH7vuFq72me27nJqObvX1qRARERmO7gKLgyGt1WVI1jZfnwoREZHh6C6wyAhvoy7Dc3YBZdwzhIiIyJt0F1gURjRFni0CJlsZkLXT16dDRERkKLoLLKLCQlRliJKxxdenQ0REZCg6DCzM2Ga1d+BkYEFERORdOgwsgrHd5ggsMhlYEBEReZP+AotQM7Y5A4sMlpwSERF5k/4Ci7BgpFlT7F9I8qal2NenREREZBi6Cyyiw4KRgTgUmqIBmxU4tt3Xp0RERGQYulwKAYJwMCTV/g0uhxAREXmN/gKLMPv2J3vMzsBC9gwhIiIib9BhYGHfBGcXHHkWmZyxICIi8hb9BRah9hkL9rIgIiLyPt3OWGwsc3TfPL4HKC307UkREREZhA4DC/uMxYHSKCAyAYANOJrm69MiIiIyBN0FFtGqKgQoKC2HLamz/ZusDCEiIvIK3c5YCEuCI7Bga28iIiKv0F1gERZsgtkUpK4XN+5o/yYTOImIiLxCd4FFUFCQo0kWkBfb3v5NLoUQERF5he4CC2dbb5Ed1c7+jZx9QEmeb0+KiIjIAHQZWDjzLHKCYoDoJvZvZrIyhIiIyNN0HVgUlJQDFZUhzLMgIiLyNF0vhRSUlAHJXe3fZGtvIiIij9NlYOHsvpmvAgvnjAU3IyMiIvI0nQYWLjMWSV3s32RlCBERkccZYCnEMWORdwgoyvbtiREREemcrmcs8iV5M7wREOvYkIx5FkRERB6l/xkLwcoQIiIi/wsspk2bpjpbuh6dOzvetP2Is/NmfqkjsEh25FlwxoKIiMijTuzYVUfdunXDL7/8cuIOgt2+C+8mb7oGFqwMISIi8ii3owIJJJo2bYrAWgphZQgREZFfBhbbt29H8+bNER4ejoEDB+Kpp55Cq1atarx9SUmJOpxyc3PVpcViUYdWnPcll86d0/OKy+zfb9wWIfKNggxYcg4DkQnQC9dxG41Rx85xc9xGYNRx+/PY63o+QTabzVbXO50/fz7y8/PRqVMnpKenY/r06Th48CA2btyImJiYGvMy5HZVzZ49G5GRkfCE3XnAjI3BSAizYWrfcvW94ZvuRlTpUfze/j84FuN/eSFERET+rLCwEBMmTEBOTg5iY2O1CSyqys7ORmpqKl544QXccMMNdZ6xSElJwdGjR2s9sfpEUgsWLMCIESOwO6sY5726HPFRIVj5wDnq5+bPxsO0YwHKRz0Da//roReu4w4JUfMyhmHUsXPcHLcRGHXc/jx2ef9OTEw8ZWDRoMzLuLg4dOzYETt27KjxNmFhYeqoSn5ZnviFyX02ikLFJmQVj9GkK7BjAcxZ22D2o38orXjq9xkIjDp2jttYOG7jCfGzsdf1XBrUx0KWRXbu3IlmzZrBn0SF2uOlkjIrysqt9m86NyNjLwsiIiKPcSuwuPfee7F48WLs2bMHy5Ytw7hx42A2mzF+/Hj4Y7lpxdbpVZtk1X/1h4iIiGrh1lLIgQMHVBBx7NgxJCUl4cwzz8SKFSvUdX8SGmxCqNmE0nKrapLVKDIESOwoKSVAURZQkAlEJ/v6NImIiIwdWMyZMweBtHV6aaH1RC+L0Eggvg2QtcveKIuBBRERkeZ0uVdI5Y3IHIGFYKMsIiIij9JtYHFS903h3EI9kwmcREREnqDbwOKk/UIEK0OIiIg8SveBRb6zKqRSZchWVoYQERF5gG4Di+gw88kzFokdgCAzUJID5KX77uSIiIh0SreBRVRoNcmbwWFAQjv7dS6HEBERac5YORZVG2URERGRpoxVFSKSHSWnrAwhIiLSnLGSN10DC/ayICIi0pyxkjddm2RlsjKEiIhIa/rPsSitElhI8qYpBCjNB3L2++bkiIiIdEr3gUWlqhBhlg3JOtivczmEiIhIU8ZL3qxUGbLZy2dFRESkbwYoN62SvFmpMoQzFkRERFrSf/Jm1RyLSpUhLDklIiLSkvEaZFWqDEkDrFYvnxkREZF+6T6wsJTbUFJWZTkkvg1gDgPKioDsPb45QSIiIh3S/V4h1eZZmMxAUkf7dVaGEBERaUa3gYXZFISIkBqaZLkuh7AyhIiISDO6DSxq7WUhkh0lp6wMISIi0oyuA4sa23qL5K72Sy6FEBERaca4MxbOJllH04Dyan5OREREbjNEYFFtk6y4VCAkEigvBY7v9v7JERER6ZCuA4ta23qbTECiszKEjbKIiIi0YNylkEp5FgwsiIiItGDc5M1KlSEMLIiIiLSg68AiytEkK7+6/UIq9bJgZQgREZEW9B1Y1JZj4boZ2bHtQFmpF8+MiIhInwySvFlNVYho1BIIjQGsZUDWTu+eHBERkQ4ZO3kzKAhI6mS/zgROIiKiBtN5YHGK5E3X5RAGFkRERA1m3D4WVQMLVoYQERE1mLGXQlxbe7MyhIiIqMGMnbzp2iRLkjctxV46MyIiIn0ydrmpiGkKhDcCbFZ72SkRERHVmzGSN0vLYLPZaqkMYaMsIiIiLRii86bVBhRZalsOYQInERGRFnQdWESGmtWExCkTOFlySkREpAldBxZBQUEVsxa1JnBWVIYwsCAiImoIXQcWdW+S5agMOb4HKC300pkRERHpjwECizr0sohOAiITANiAo2neOzkiIiKd0X1gUafum4KVIURERA2m+8AiKrQOMxaClSFERES+DSyefvpplSD5r3/9C/7fJKuW5E2RzAROIiIinwUWq1evxptvvomePXvCn0XXJXlTcCmEiIiowewf592Un5+PiRMn4u2338bjjz9e621LSkrU4ZSbm6suLRaLOrTivK+q9xkRYo+dcotKan+8xu0RIpc5+2DJzwLCYhAIahq3ERh17Bw3x20ERh23P4+9rucTZKux13XNJk+ejPj4eLz44osYOnQoevfujRkzZlR722nTpmH69OknfX/27NmIjIyEp32714TfDpkwtJkV41pba73tqA23I7wsB0s6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pdsUFxfjtttuQ0JCAqKjo3HppZfiyJEjlW6zb98+nHfeeYiMjFT3c99996GsrKzSbRYtWoS+ffuqbOP27dtj1qxZ8BdPP/206sb6r3/9S/fjPnjwIK6++mo1roiICPTo0QNr1qyp+LnkVE+dOhXNmjVTPx8+fDi2b99e6T6ysrIwceJE1UAnLi4ON9xwA/Lz8yvdZv369TjrrLPU34V08nvmmWfgK+Xl5Xj44YfRpk0bNaZ27drhscceq7TvgF7GvWTJElxwwQWqQ6E8p7/55ptKP/fmOL/44gt07txZ3UaeZ/PmzfPJuGX/i/vvv1+dQ1RUlLrNpEmTVCdmPY+7qptvvlndZsaMGQE/7hrZdGDOnDm20NBQ23vvvWfbtGmT7cYbb7TFxcXZjhw5YgsEo0aNsr3//vu2jRs32tatW2cbO3asrVWrVrb8/PyK29x88822lJQU26+//mpbs2aN7YwzzrANGjSo4udlZWW27t2724YPH27766+/bPPmzbMlJibaHnzwwYrb7Nq1yxYZGWm7++67bZs3b7a98sorNrPZbPvxxx9tvrZq1Spb69atbT179rTdeeeduh53VlaWLTU11XbttdfaVq5cqc7vp59+su3YsaPiNk8//bStUaNGtm+++cb2999/2y688EJbmzZtbEVFRRW3GT16tK1Xr162FStW2JYuXWpr3769bfz48RU/z8nJsTVp0sQ2ceJE9dz69NNPbREREbY333zT5gtPPPGELSEhwfb999/bdu/ebfviiy9s0dHRtpdeekl345bn4f/93//Zvv76a4mabHPnzq30c2+N848//lDP9WeeeUY99x966CFbSEiIbcOGDV4fd3Z2tvo7/eyzz2xbt261LV++3Hb66afb+vXrV+k+9DZuV/JzGVvz5s1tL774YsCPuya6CCzkyXnbbbdVfF1eXq7+4Z566ilbIMrIyFBPzsWLF1f8QcqTQ16InbZs2aJuI3+czie2yWSyHT58uOI2M2fOtMXGxtpKSkrU1//+979t3bp1q/RYV155pQpsfCkvL8/WoUMH24IFC2xnn312RWCh13Hff//9tjPPPLPGn1utVlvTpk1tzz77bMX35HcRFhamXkyEvGjI72H16tUVt5k/f74tKCjIdvDgQfX166+/bmvcuHHF78H52J06dbL5wnnnnWe7/vrrK33vkksuUS+U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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 14
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T10:16:53.839692Z",
     "start_time": "2025-01-26T10:16:53.755463Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4249\n"
     ]
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": ""
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
